{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "efd74884",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com\n",
      "Collecting numpy\n",
      "  Downloading numpy-2.2.6-cp310-cp310-win_amd64.whl (12.9 MB)\n",
      "Collecting pandas\n",
      "  Downloading pandas-2.2.3-cp310-cp310-win_amd64.whl (11.6 MB)\n",
      "Collecting scipy\n",
      "  Downloading scipy-1.15.3-cp310-cp310-win_amd64.whl (41.3 MB)\n",
      "Collecting scikit-learn\n",
      "  Downloading scikit_learn-1.6.1-cp310-cp310-win_amd64.whl (11.1 MB)\n",
      "Requirement already satisfied: python-dateutil>=2.8.2 in .\\heart_env\\lib\\site-packages (from pandas) (2.9.0.post0)\n",
      "Collecting pytz>=2020.1\n",
      "  Downloading pytz-2025.2-py2.py3-none-any.whl (509 kB)\n",
      "Collecting tzdata>=2022.7\n",
      "  Downloading tzdata-2025.2-py2.py3-none-any.whl (347 kB)\n",
      "Collecting threadpoolctl>=3.1.0\n",
      "  Downloading threadpoolctl-3.6.0-py3-none-any.whl (18 kB)\n",
      "Collecting joblib>=1.2.0\n",
      "  Downloading joblib-1.5.1-py3-none-any.whl (307 kB)\n",
      "Requirement already satisfied: six>=1.5 in .\\heart_env\\lib\\site-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)\n",
      "Installing collected packages: numpy, tzdata, threadpoolctl, scipy, pytz, joblib, scikit-learn, pandas\n",
      "Successfully installed joblib-1.5.1 numpy-2.2.6 pandas-2.2.3 pytz-2025.2 scikit-learn-1.6.1 scipy-1.15.3 threadpoolctl-3.6.0 tzdata-2025.2\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: You are using pip version 21.2.3; however, version 25.1.1 is available.\n",
      "You should consider upgrading via the 'd:\\priyanka\\heart_env\\Scripts\\python.exe -m pip install --upgrade pip' command.\n"
     ]
    }
   ],
   "source": [
    "pip install numpy pandas scipy scikit-learn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "46c2c343",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com\n",
      "Collecting PyWavelets\n",
      "  Downloading pywavelets-1.8.0-cp310-cp310-win_amd64.whl (4.2 MB)\n",
      "Requirement already satisfied: numpy<3,>=1.23 in .\\heart_env\\lib\\site-packages (from PyWavelets) (2.2.6)\n",
      "Installing collected packages: PyWavelets\n",
      "Successfully installed PyWavelets-1.8.0\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: You are using pip version 21.2.3; however, version 25.1.1 is available.\n",
      "You should consider upgrading via the 'd:\\priyanka\\heart_env\\Scripts\\python.exe -m pip install --upgrade pip' command.\n"
     ]
    }
   ],
   "source": [
    "pip install PyWavelets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6e9918b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from scipy.signal import butter, filtfilt, find_peaks\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n",
    "from scipy.stats import pearsonr\n",
    "import pywt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "876970e7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🔄 Generating synthetic PPG and ECG data...\n"
     ]
    }
   ],
   "source": [
    "# 1. Generate Sample Data (PPG & ECG)\n",
    "print(\"🔄 Generating synthetic PPG and ECG data...\")\n",
    "fs = 100  # Sampling frequency in Hz\n",
    "duration = 60  # Duration in seconds\n",
    "t = np.linspace(0, duration, fs * duration)\n",
    "ppg_signal = 0.6 * np.sin(2 * np.pi * 1.2 * t) + 0.3 * np.random.randn(len(t))  # Simulated PPG\n",
    "ecg_heart_rate = 70 + 5 * np.sin(2 * np.pi * 0.1 * t)  # Simulated ECG-derived HR (ground truth"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "47f56503",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🔬 Applying Butterworth bandpass filter...\n",
      "🌀 Applying Wavelet denoising...\n"
     ]
    }
   ],
   "source": [
    "# 2. Butterworth Bandpass Filter\n",
    "print(\"🔬 Applying Butterworth bandpass filter...\")\n",
    "\n",
    "def butter_bandpass_filter(data, lowcut=0.5, highcut=5.0, fs=100, order=3):\n",
    "    nyquist = 0.5 * fs\n",
    "    low = lowcut / nyquist\n",
    "    high = highcut / nyquist\n",
    "    b, a = butter(order, [low, high], btype='band')\n",
    "    return filtfilt(b, a, data)\n",
    "\n",
    "filtered_ppg = butter_bandpass_filter(ppg_signal)\n",
    "\n",
    "# Additional Preprocessing: Wavelet Denoising for motion artifact/noise removal\n",
    "print(\"🌀 Applying Wavelet denoising...\")\n",
    "\n",
    "def wavelet_denoise(data, wavelet='db4', level=1):\n",
    "    coeffs = pywt.wavedec(data, wavelet, mode='per')\n",
    "    sigma = np.median(np.abs(coeffs[-level])) / 0.6745\n",
    "    uthresh = sigma * np.sqrt(2 * np.log(len(data)))\n",
    "    coeffs[1:] = [pywt.threshold(i, value=uthresh, mode='soft') for i in coeffs[1:]]\n",
    "    return pywt.waverec(coeffs, wavelet, mode='per')\n",
    "\n",
    "denoised_ppg = wavelet_denoise(filtered_ppg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "07104988",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📈 Extracting features from filtered PPG signal...\n",
      "✅ Features and labels prepared.\n",
      "     mean_hr    std_hr  peak_count  signal_power\n",
      "0  73.165896  6.306596           6      0.180463\n",
      "1  70.874977  2.879250           6      0.183654\n",
      "2  71.831357  5.380977           6      0.194606\n",
      "3  73.469497  2.964328           6      0.183106\n",
      "4  70.912281  4.825071           6      0.170740\n"
     ]
    }
   ],
   "source": [
    "# 3. Feature Extraction\n",
    "print(\"📈 Extracting features from filtered PPG signal...\")\n",
    "\n",
    "def extract_features(ppg_segment, fs=100):\n",
    "    peaks, _ = find_peaks(ppg_segment, distance=fs*0.6)\n",
    "    if len(peaks) < 2:\n",
    "        return None  # Not enough peaks to compute features\n",
    "    rr_intervals = np.diff(peaks) / fs\n",
    "    hr_estimates = 60 / rr_intervals\n",
    "    return {\n",
    "        'mean_hr': np.mean(hr_estimates),\n",
    "        'std_hr': np.std(hr_estimates),\n",
    "        'peak_count': len(peaks),\n",
    "        'signal_power': np.mean(ppg_segment ** 2),\n",
    "    }\n",
    "\n",
    "# Segment-wise feature extraction\n",
    "window_size = fs * 5  # 5-second windows\n",
    "features = []\n",
    "labels = []\n",
    "\n",
    "for i in range(0, len(denoised_ppg) - window_size, window_size):\n",
    "    segment = denoised_ppg[i:i + window_size]\n",
    "    feature_dict = extract_features(segment)\n",
    "    if feature_dict:\n",
    "        features.append(feature_dict)\n",
    "        labels.append(np.mean(ecg_heart_rate[i:i + window_size]))\n",
    "\n",
    "X = pd.DataFrame(features)\n",
    "y = pd.Series(labels)\n",
    "\n",
    "print(\"✅ Features and labels prepared.\")\n",
    "print(X.head())\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "ea60f478",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🤖 Training Random Forest Regressor...\n",
      "✅ Model training complete.\n"
     ]
    }
   ],
   "source": [
    "# 4. Model Training\n",
    "print(\"🤖 Training Random Forest Regressor...\")\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
    "model = RandomForestRegressor(n_estimators=100, random_state=42)\n",
    "model.fit(X_train, y_train)\n",
    "\n",
    "print(\"✅ Model training complete.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "acd3f1e9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "📊 Evaluating model...\n",
      "📈 MAE: 3.99 BPM\n",
      "📉 RMSE: 4.33 BPM\n",
      "📊 R² (Coefficient of Determination): -1.09\n",
      "🔗 Pearson Correlation Coefficient: -0.56\n"
     ]
    }
   ],
   "source": [
    "# 5. Evaluation\n",
    "print(\"📊 Evaluating model...\")\n",
    "\n",
    "y_pred = model.predict(X_test)\n",
    "mae = mean_absolute_error(y_test, y_pred)\n",
    "rmse = np.sqrt(mean_squared_error(y_test, y_pred))\n",
    "r2 = r2_score(y_test, y_pred)\n",
    "corr, _ = pearsonr(y_test, y_pred)\n",
    "\n",
    "print(f\"📈 MAE: {mae:.2f} BPM\")\n",
    "print(f\"📉 RMSE: {rmse:.2f} BPM\")\n",
    "print(f\"📊 R² (Coefficient of Determination): {r2:.2f}\")\n",
    "print(f\"🔗 Pearson Correlation Coefficient: {corr:.2f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4a12e77b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🎯 Predicted Heart Rate on new segment: 68.66 BPM\n"
     ]
    }
   ],
   "source": [
    "# 6. Prediction Module\n",
    "def predict_heart_rate(ppg_new_segment, fs=100):\n",
    "    # Preprocess new segment\n",
    "    filtered = butter_bandpass_filter(ppg_new_segment, fs=fs)\n",
    "    denoised = wavelet_denoise(filtered)\n",
    "    features = extract_features(denoised, fs=fs)\n",
    "    if features is None:\n",
    "        print(\"⚠️ Not enough peaks detected for prediction.\")\n",
    "        return None\n",
    "    feature_df = pd.DataFrame([features])\n",
    "    predicted_hr = model.predict(feature_df)[0]\n",
    "    return predicted_hr\n",
    "\n",
    "# Example of prediction with last 5 seconds of data\n",
    "new_ppg_segment = ppg_signal[-window_size:]\n",
    "predicted_hr_example = predict_heart_rate(new_ppg_segment)\n",
    "print(f\"🎯 Predicted Heart Rate on new segment: {predicted_hr_example:.2f} BPM\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "heart_env",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
